<?php
/**
 * XGPHP 轻量级PHP框架
 * @link http://xgphp.xg3.cn
 * @version 2.0.0
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @author 讯高科技 <xungaokeji@qq.com>
*/
namespace xg;
use \helper\Arr;
use \helper\Str;
use \helper\Http;
use \helper\File as FileHelper;
use \xg\facade\Lang;
use \xg\facade\Config;
use \xg\facade\Request;
use \xg\facade\Response;

class File {
	protected $config=[];
	protected $private=false;
	protected $type=null;
	protected $ext='';
	protected $disallow=['php','phar','php4','php3','php2','phtml','asp','html','htm','shtml','shtm','stm','ashx','aspx','jsp','exe','bat','vbs','vbe','cmd','py','rb','pyc','e','cgi','sh','js','css','conf','vue'];
	protected $allow=[];
	protected $max=0;
	protected $filename=null;
	protected $savepath=null;
 	public function __construct($type=null){
		$this->type=$type;
		if(!$this->type){//如果没有指定文件类型 从请求中获取文件类型
			$this->type=Request::input('upload-type');
		}
		if(Request::input('upload-private')){//请求中标记为私密文件
			$this->private=true;
		}
		$this->config=Config::get('file');//读取文件设置信息
		if($type_config=$this->config[$this->type] and is_array($type_config)){
			//如果存在此类型上传设置
			if($type_config['allow']) {
				$this->allow=Arr::extend($this->allow,$type_config['allow']);
			}
			if($type_config['max']) {
				$this->max=$type_config['max'];
			}
		}elseif(is_array($this->config)){
			//默认文件设置
			if($this->config['allow']) {
				$this->allow=Arr::extend($this->allow,$this->config['allow']);
			}
			if($this->config['max']) {
				$this->max=$this->config['max'];
			}
		}
		//系统设置中的文件类型
		if($site_allow=Config::get('site.upload-'.$this->type.'-allow')){
			$this->allow=Arr::extend($this->allow,$site_allow);
		}
		//系统设置中的文件大小
		if($site_max=Config::get('site.upload-'.$this->type.'-max')){
			$this->max=$site_max;
		}
		if($this->max){
			//转换文件大小为真实byte
			$this->max=Str::toByte($this->max);
		}
	}
	//验证上传的文件
	protected function valiFile($file=null){
		if(!$file)$file=$_FILES;
		if($file['file']['size']<=0){
			$this->error('upload file is empty');
		}
		if($file['file']['error']){
			$code=array(
				1 => 'upload file size exceeds the maximum value',
				2 => 'form data size exceeds the maximum value',
				3 => 'only the portion of file is uploaded',
				4 => 'no file to uploaded',
				6 => 'upload temp dir not found',
				7 => 'file write error'
			);
			//已知错误提示
			if($code[$file['file']['error']]){
				$this->error($code[$file['file']['error']]);
			}
			//未知错误提示
			$this->error('unknown upload error code:{:code}',['code'=>$file['file']['error']]);
		}
		$ext=FileHelper::ext($file['file']['name']);//文件扩展名
		$this->valiExt($ext);//验证文件类型
		$this->valiSize($file['file']['size']);//验证文件大小
	}
	//验证文件大小
 	protected function valiSize($size){
		if($this->max and $size > $this->max){
			$this->error('upload exceeds admin limit');
		}
	}
	//验证类型
	protected function valiExt($ext){
		if(in_array($ext,$this->disallow)){
			$this->error('upload denied "{:ext}"',['allow'=>$ext]);
		}
		if(!in_array($ext,$this->allow)){
			if($this->type=='video'){
				$this->error('invalid video format');
			}elseif($this->type=='image'){
				$this->error('invalid image format');
			}else{
				$this->error('unsupported file type "{:ext}"',['ext'=>$ext]);
			}
		}
	}
	//添加允许上传的文件类型
 	public function allow($ext){
		if($ext){
			// //只允许添加json和svg文件
			// if(in_array($ext,['json','svg'])){
			// 	$this->disallow=array_diff($this->disallow,[$ext]);
			// }
			$this->allow[]=$ext;
		}
		return $this;
	}
	//根据请求类型 输出错误信息
 	protected function error($msg,$data=[]){
		$msg=Lang::get($msg,$data);
		Response::create('result')->error($msg)->done();
	}
	//设置是否是私有文件
 	public function private($private=true){
		$this->private=$private;
		return $this;
	}
	//获取上传文件的文件内容
 	public function content($file=null,$vali=true){
		if(!$file)$file=$_FILES;
		if($vali)$this->valiFile($file);
		return FileHelper::cont($file['file']['tmp_name']);
	}
	//指定保存文件的名称
 	public function filename($filename){
		$this->filename=$filename;
		return $this;
	}
	public function savepath($path){
	   $this->savepath=$path;
	   return $this;
   }
	//下载远程文件
 	public function download($url=null){
		//检查地址格式
		if(substr($url,0,7)!='http://' and substr($url,0,8)!='https://'){
			$this->error('invalid url');
		}
		
		$ext=strtolower(end(Arr::split(Arr::split($url,'?')[0],'.')));
		//验证文件类型
		$this->valiExt($ext);

		//加载文件内容
		$filecont=Http::get($url);
		if(!$filecont){
			$this->error('download failed');
		}

		$filesize = is_string($filecont) ? strlen($filecont) : 0; // 获取文件字节大小，确保输入为字符串类型
		$this->valiSize($filesize);//验证文件大小

		$fileinfo = $this->fileinfo($ext);//获取文件保存信息
		
		if(!FileHelper::cont($fileinfo['savepath'],$filecont)){//保存文件
			$this->error('file write error');
		}

		$fileinfo['filesize'] = $filesize;

		//计算文件哈希值
		if($this->config['calc_hash']){
			$fileinfo['md5']=md5_file($fileinfo['savepath']);
			$fileinfo['sha1']=sha1_file($fileinfo['savepath']);
		}
		unset($fileinfo['savepath']);
		return $fileinfo;
	}
	//分块上传处理
 	public function chunk(){
		$uuid=Request::input('uuid');//上传文件的唯一ID 用来区分不同的上传文件
		$chunk=Request::input('chunk/i',0);//当前是第几个分块
		$chunkSize=Request::input('chunkSize/i',0);//当前分块的大小
		$totalChunks=Request::input('totalChunks/i',0);//总分块数
		$uploadFileName=Request::input('fileName');//上传文件的名称
		$uploadFileSize=Request::input('fileSize/i',0);//当前上传文件的总大小
		if(!$uuid){
			$this->error('miss upload uuid');
		}
		$ext=FileHelper::ext($uploadFileName);
		$this->valiExt($ext);//验证上传文件类型
		$this->valiSize($uploadFileSize);//验证上传文件大小
		$uploadTempPath=sys_get_temp_dir().XG_DS.md5($uuid);//拼接文件路径
		$newFileData=file_get_contents($_FILES['file']['tmp_name']);//获取分块文件的内容
		unlink($_FILES['file']['tmp_name']);//删除分块临时文件
		file_put_contents($uploadTempPath,$newFileData,FILE_APPEND);//追加上传分块内容到拼接文件
		if($chunk==$totalChunks-1){//如果全部上传完成
			$fileinfo = $this->fileinfo($ext);//获取文件保存信息
			
			if(!$this->savefile($uploadTempPath,$fileinfo['savepath'])){//保存文件
				$this->error('file write error');
			}

			$fileinfo['filesize'] = $uploadFileSize;
			$fileinfo['name'] = $uploadFileName;

			//计算文件哈希值
			if($this->config['calc_hash']){
				$fileinfo['md5']=md5_file($fileinfo['savepath']);
				$fileinfo['sha1']=sha1_file($fileinfo['savepath']);
			}

			unset($fileinfo['savepath']);
			return $fileinfo;
		}
		$this->error('chunk {:index} uploaded',['index'=>$chunk+1]);
	}
	//获取文件保存信息
 	protected function fileinfo($ext){
		if($this->private){//私有文件
			if($this->config['private_path']){
				$savepath=$this->config['private_path'];
			}else{
				$savepath=FileHelper::path('xg:data');
			}
		}elseif($this->config['abspath']){//绝对路径
			$savepath=$this->config['abspath'];
		}elseif($this->config['path']){//设置中的上传目录
			$filepath=$this->config['path'];
			$savepath=FileHelper::path($filepath);
		}else{//默认上传目录
			$filepath='/upload/';
			$savepath=FileHelper::path($filepath);
		}
		mkdir($savepath,0755,true);//创建目录
		if($this->filename){//自定义文件名称
			$filename=$this->filename;
		}else{
			$filename=$this->createName($ext);//生成文件名称
		}
		
		return [
			'savepath' => FileHelper::path($savepath,$filename),
			'url' => str_replace('\\','/',FileHelper::path($filepath,$filename)),
			'filename' => $filename,
			'ext' => $ext,
		];
	}
	//保存文件
 	protected function savefile($file,$savepath,$safe=false){
		$savepath=FileHelper::path(dirname($_SERVER['SCRIPT_FILENAME']),$savepath);
		$savedir=dirname($savepath);
		FileHelper::mkdir($savedir);
		if(is_uploaded_file($file)){
			move_uploaded_file($file,$savepath);
		}elseif($safe){
			$saved=copy($file,$savepath);
			if($saved){
				unlink($file);
			}
		}
		$saved = is_file($savepath);
		if(!$saved){
			$this->error('file write error');
		}
		if($this->config['resize']){
			$this->resize($savepath,$this->config['resize']);
		}
		return $saved;
	}
	//上传操作
 	public function upload($file=null){
		if(!$file)$file=$_FILES;
		$this->valiFile($file);
		$ext=FileHelper::ext($file['file']['name']);

		$fileinfo = $this->fileinfo($ext);//获取文件保存信息
			
		$this->savefile($file['file']['tmp_name'],$fileinfo['savepath']);

		$fileinfo['filesize'] = intval($file['file']['size']);
		$fileinfo['name'] = $file['file']['name'];

		//计算文件哈希值
		if($this->config['calc_hash']){
			$fileinfo['md5']=md5_file($fileinfo['savepath']);
			$fileinfo['sha1']=sha1_file($fileinfo['savepath']);
		}
		unset($fileinfo['savepath']);
		return $fileinfo;
	}
	//修改设置信息
	public function config($key,$value=null){
		if(is_array($key)){
			$this->config=Arr::extend($this->config,$key);
		}else{
			$this->config[$key]=$value;
		}
		$this->allow = Arr::split($this->config['allow']);
		return $this;
	}
	//生成文件名称
	protected function createName($ext){
		if($filename=$this->config['filename']){
			if(preg_match('/\[num(?:\:([0-9]+))?\]/',$filename,$matches)){
				$randstr=Str::rand($matches[1]?intval($matches[1]):16,'n');
				$filename=str_replace($matches[0],$randstr,$filename);
			}
			if(preg_match('/\[rand(?:\:([0-9]+))?\]/',$filename,$matches)){
				$randstr=Str::rand($matches[1]?intval($matches[1]):16);
				$filename=str_replace($matches[0],$randstr,$filename);
			}
			while(preg_match('/\[([ymdhis]+)\]/i',$filename,$matches)){
				$timestr='';
				for ($i=0;$i<strlen($matches[1]);$i++) {
					$timestr.=date(substr($matches[1],$i,1));
				}
				$filename=str_replace($matches[0],$timestr,$filename);
			}
			$filename=$filename.'.'.$ext;
		}else{
			$filename=date('YmdHis').Str::rand(4).'.'.$ext;
		}
		return $filename;
	}
	//图片裁剪操作
	public function resize($filepath,$resize){
		$ext=FileHelper::ext($filepath);
		if(in_array($ext,array('png','gif','jpg','jpeg'))){
			if(strpos($resize,'*')>-1){
				$resize=Arr::split($resize,'*');
				list($width,$height)=$resize;
				$scale=false;
			}elseif(strpos($resize,'x')>-1){
				$resize=Arr::split($resize,'x');
				list($width,$height)=$resize;
				$scale=false;
			}elseif(is_numeric($resize)){
				$width=$height=$resize;
				$scale=true;
			}
			if($width and $height){
				$this->image_resize($filepath,$filepath,$width,$height,$scale);
			}
		}
		return $this;
	}

	public function image_resize($source_path,$target_path,$target_width,$target_height,$scale=false){
		$source_info=getimagesize($source_path);
		$source_width=$source_info[0];
		$source_height=$source_info[1];
		$source_mime=$source_info['mime'];
		
		if($scale){
			$width_ratio=$source_width / $target_width;
			$height_ratio=$source_height / $target_height;
			if($width_ratio>$height_ratio){
				$target_width=$source_width / $width_ratio;
				$target_height=$source_height / $width_ratio;
			}else{
				$target_width=$source_width / $height_ratio;
				$target_height=$source_height / $height_ratio;
			}
			if($target_width>=$source_width){
				$target_width=$source_width;
				$target_height=$source_height;
				//return false;
			}
			$cropped_width=$source_width;
			$cropped_height=$source_height;
			$source_x=0;
			$source_y=0;
		}else{
			$source_ratio=$source_height / $source_width;
			$target_ratio=$target_height / $target_width;
			// 源图过高
			if($source_ratio>$target_ratio){
				$cropped_width=$source_width;
				$cropped_height=$source_width * $target_ratio;
				$source_x=0;
				$source_y=($source_height-$cropped_height)/ 2;
			}elseif($source_ratio<$target_ratio){ // 源图过宽
				$cropped_width=$source_height / $target_ratio;
				$cropped_height=$source_height;
				$source_x=($source_width-$cropped_width)/ 2;
				$source_y=0;
			}else{ // 源图适中
				$cropped_width=$source_width;
				$cropped_height=$source_height;
				$source_x=0;
				$source_y=0;
			}
		}
		
		switch($source_mime){
			case 'image/gif':
				$source_image=imagecreatefromgif($source_path);
				break;
			
			case 'image/jpeg':
				$source_image=imagecreatefromjpeg($source_path);
				break;
			
			case 'image/png':
				$source_image=imagecreatefrompng($source_path);
				break;
			
			default:
				return false;
				break;
		}
		$target_image=imagecreatetruecolor($target_width,$target_height);
		$cropped_image=imagecreatetruecolor($cropped_width,$cropped_height);
		
		// 裁剪
		imagecopy($cropped_image,$source_image,0,0,$source_x,$source_y,$cropped_width,$cropped_height);
		// 缩放
		imagecopyresampled($target_image,$cropped_image,0,0,0,0,$target_width,$target_height,$cropped_width,$cropped_height);
		$target_type=FileHelper::ext($target_path);
		switch($target_type){
			case 'gif':
				imagegif($target_image,$target_path);
				break;
			
			case 'jpeg':
				imagejpeg($target_image,$target_path,90);
				break;
			
			case 'jpg':
				imagejpeg($target_image,$target_path,90);
				break;
			
			case 'png':
				imagepng($target_image,$target_path);
				break;
			
			default:
				imagejpeg($target_image,$target_path,90);
				break;
		}
		imagedestroy($source_image);
		imagedestroy($target_image);
		imagedestroy($cropped_image);
		return true;
	}
	//png转jpg
	public function png2jpg($fileurl){
		$ext=FileHelper::ext($fileurl);
		if($ext=='png'){
			$filepath=$fileurl;
			$data=imagecreatefrompng($filepath);
			if($data){
				unlink($filepath);
				$fileurl=substr($fileurl,0,-3).'jpg';
				$filepath=substr($filepath,0,-3).'jpg';
				imagejpeg($data,$filepath,90);
			}
		}
		return $fileurl;
	}
}
?>